/* Copyright (c) CERN
 * * Artistic License 2.0
 * */

import java.util.Arrays;

/**
 * This class contains methods used by various distributions.
 *
 * @author Xavier Grehant
 */
public abstract class DistroUtils {

/**
 * Each distribution must have its random number generator.
 */
   public abstract double nextDouble();

/**
 * This method deletes isolated groups of samples with highest values.
 * These samples may be considered as noise and for some distribution, badly
 * alter the estimation, i.e. when the maximum value is used to calculate
 * parameters of the distribution.
 */
   int[] discard(int[] array, double maxprob, double minstd) {
      Arrays.sort(array);
      array = flip(array);

      boolean continuing = true;
      while (continuing) { // while we kill we keep going
         System.out.println("Considering " + (int) array.length * maxprob + " samples for deletion");
         int[] considered = Arrays.copyOfRange(array, 0, (int) (array.length * maxprob) - 1);
         double oldDiffstd = 0;
         double oldstd = 0;
         for (int i = 1; i < considered.length; i++) {
            continuing = false; // supposing we don't find a group to kill
            int[] considerednow = Arrays.copyOfRange(considered, 0, i);
            double newstd = std(considerednow, mean(considerednow));
            System.out.println("oldDiffstd = " + oldDiffstd + " oldstd = " 
                               + oldstd + " newstd = " + newstd + " minstd = " + minstd);
            if (newstd - oldstd < oldDiffstd && oldstd > minstd) {
               array = Arrays.copyOfRange(array, i - 1, array.length - 1);
               System.out.println("Discarded : " + Arrays.copyOfRange(array, 0, i - 2));
               continuing = true; // found one. There might be more.
               break;
            }
            oldDiffstd = newstd - oldstd;
            oldstd = newstd;
         }
      }
      return array;
   }

/**
 * Mean
 * @return the mean.
 * @param array an array of integers.
 */
   double mean(int[] array) {
      double mean = 0d;
      for (int i = 0; i < array.length; i++) {
         mean += (double) array[i] / (double) array.length;
      }
      return mean;
   }

/**
 * Variance
 * @return the variance.
 * @param array an array of integers.
 * @param mean the mean of the array.
 */
   double var(int[] array, double mean) {
      double variance = 0d;
      for (int i = 0; i < array.length; i++) {
         variance += Math.pow((double) array[i] - mean, 2d) / (double) array.length;
      }
      return variance;
   }

/**
 * Standard deviation
 * @return the standard deviation.
 * @param array an array of integers.
 * @param mean the mean of the array.
 */
   double std(int[] array, double mean) {
      return Math.sqrt(var(array, mean));
   }

/**
 * This method flips an array.
 * @return the flipped array.
 * @param the initial array.
 */
   int[] flip(int[] array) {
      int[] flippy = new int[array.length];
      for (int i = 0; i < array.length; i++)
         flippy[i] = array[array.length - 1 - i];
      return flippy;
   }

/**
 * This method biases a sample set to better fit samples over 'minvalue'.
 * @return a new sample set with artificial values under 'minvalue'.
 *         These values are random values generated according to the best fit.
 * @param values the sample set
 * @param minvalue the minimum value of interest.
 */
   int[] bias(int[] values, int minvalue) {
                for (int index = 0; index < values.length; index++) {
                    if (values[index] < minvalue)
                        values[index] = nextIntSmallerThan(minvalue);
                }
                return values;
   }

/**
 * Random integer generator according to the distribution below a limit.
 * @return a random integer according to the distribution below 'max'.
 * @param max the upper limit.
 */
  public int nextIntSmallerThan(int max) {
      int result;
      while ((result = (int) nextDouble()) >= max);
      return result;
   }
}
